#include <algorithm>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include "Sender.h"
#include "SenderError.h"
#include "../../config/Config.h"
#include "../../mime/core/Base64Coder.h"
#include "../../system/Observers.h"

#define foreach BOOST_FOREACH

using namespace kc_sf;

// ------------------------------------------------------ //

Sender::Sender()
{
	s_fqdn = Config::getInstance().cMyDomain;
}

// ------------------------------------------------------ //

void Sender::connectToServer( const std::string &srv_name, int port )
{
	try
	{
		s_client.connectToSrv( srv_name, port );
		//s_client.initSSLContext();
	}
	catch( CommError &err )
	{
		disconnect();
		s_logger << error << sender << cidOut( s_client.connectionId() ) << err.what() << flush;
	}
}

// ------------------------------------------------------ //

void Sender::disconnect ()
{
	try
	{
		s_client.disconnect();
	}
	catch( CommError &err )
	{
		s_logger << error << sender << cidOut( s_client.connectionId() ) << err.what() << flush;
	}
}

// ------------------------------------------------------ //

bool Sender::sendMail( const RawMessage &msg )
{
	try
	{
		if( !s_client.isConnected() )
		{
			s_logger << error << sender << cidOut( s_client.connectionId() ) << "Błąd przy wysyłaniu wiadomości. Brak połączenia z serwerem" << flush;
			return false;
		}

		if( !recvGreetings() ||  !sendEhlo() )
		{
			sendQuit();
			s_logger << error << sender << cidOut( s_client.connectionId() ) << "Serwer nie obsługuje SMTP" << flush;
			return false;
		}

		if( sendMailFrom( msg ) && sendToRcpts( msg ) &&sendData( msg ) && sendQuit() )
			return true;

		sendQuit();
		s_logger << error << sender << cidOut( s_client.connectionId() ) << "Błąd przy wysyłaniu wiadomości" << flush;
	}
	catch( CommError &err )
	{
		disconnect();
		s_logger << error << sender << cidOut( s_client.connectionId() ) << err.what() << flush;
	}

	return false;
}

// ------------------------------------------------------ //

bool Sender::recvGreetings()
{
    // czekaj na powitanie
    Reply reply;
    if( !recvReply( reply, 5 * 60 ) || reply.Code != 220 )
    {
    	s_logger << error << sender << cidOut( s_client.connectionId() ) << "Brak powitania lub niepoprawne powitanie od serwera" << flush;
    	return false;
    }

    return true;
}

// ------------------------------------------------------ //

bool Sender::sendHelo()
{
	bool success = false;
    sendCommand( Command( Helo, s_fqdn ) );

    Reply reply;
    if( recvReply( reply ) )
    	success = reply.Code / 100 == 2 ? true : false;

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę HELO" << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendEhlo()
{
	bool success = false;

    sendCommand( Command( Ehlo, s_fqdn ) );

    Reply reply;
    if( recvReply( reply ) )
    	success = reply.Code / 100 == 2 ? true : sendHelo();

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę EHLO" << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendAuthPlain()
{
	bool success = false;

	// \0login\0haslo
	// \0login\@domena.pl\0haslo
	std::string
		login = "\0kc.smtp.test\\@gmail.com",
		passwd = "\0AlaMaKota",
		base64_encoded;
	Base64Coder coder;
	coder.encode( login + passwd, base64_encoded );

	sendCommand( Command( AuthPlain ) );

    Reply reply;
    if( recvReply( reply ) )
    	success = reply.Code / 100 == 2 ? true : false;

    if( success )
    {
		sendText( base64_encoded );

		Reply reply;
		if( recvReply( reply ) )
			success = reply.Code / 100 == 2 ? true : false;
    }

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę AUTH PLAIN " << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendMailFrom( const RawMessage &msg )
{
    sendCommand( Command( Mail, msg.rmMailFrom.toString() ) );

    Reply reply;
    bool success = false;
    if( recvReply( reply, 5 * 60 ) )
    	success = reply.Code / 100 == 2 ? true : false;

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę MAIL FROM" << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendToRcpts( const RawMessage &msg )
{
    bool success = true;

    foreach( Address address, msg.rmRcptsTo )
    {
        sendCommand( Command( Rcpt, address.toString() ) );

        Reply reply;
        if( recvReply( reply, 5 * 60 ) )
        {
            success &= reply.Code == 250 ? true : false;
            if (!success)
                break;
        }
    }

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę RCPT " << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendData( const RawMessage &msg )
{
    bool success = false;

    sendCommand( Command( Data, "" ) );

    Reply reply;
    if( recvReply( reply, 2 * 60 ) )
    {
        if( reply.Code / 100 == 3 ) /// poprawic na retry
        {
            s_client.sendData( msg.rmData, 3 * 60 );
            s_client.sendData( CRLF + "." + CRLF, 10 * 60 ); // znacznik końca nie należy do wiadomości

			if( recvReply( reply ) )
			{
				success = reply.Code / 100 == 2 /// poprawic na retry
						? true : false;
			}
        }
    }

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę DATA " << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::sendQuit()
{
    sendCommand( Command( Quit, "" ) );

    Reply reply;
    bool success = recvReply( reply );

    if( !success )
    {
    	s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Brak lub negatywna odpowiedź na komendę QUIT. Przerwano połączenie! " << flush;
    }

    return success;
}

// ------------------------------------------------------ //

bool Sender::recvReply( Reply &reply, int timeout_s )
{
    std::string cache, buffer;
    Reply::TextLinesCollection lines;

    bool success = false;
	s_client.recvData( buffer, CRLF, timeout_s );

	std::vector <std::string> buffer_lines;
	boost::split( buffer_lines, buffer, boost::is_any_of( "\r\n" ) );
	buffer_lines.erase( std::remove( buffer_lines.begin(), buffer_lines.end(), "" ), buffer_lines.end() );

	lines.insert( lines.end(), buffer_lines.begin(), buffer_lines.end() );
	const std::string &last_line = lines[ lines.size() - 1 ];
	if( last_line.size() <= 3 || ( last_line.size() > 3 && last_line[3] == ' ' ) ) // ostatnia linia - sam kod albo po kodzie spacja
	{
		std::string code = last_line.substr( 0, 3 );

		for( std::vector<std::string>::iterator it = lines.begin(); it != lines.end(); ++it )
		{
			*it = it->substr(4, it->size() - 4);
		}

		if( code.size() == 3 )
		{
			try
			{
				reply = Reply( boost::lexical_cast<int>( code ), lines );
				success = true;
			}
			catch( boost::bad_lexical_cast &err )
			{
				s_logger << detail << sender << cidOut( s_client.connectionId() ) << "Otrzymano od serwera odpowiedz, w ktorej brakuje kodu. " << flush;
			}
		}
	}

    return success;
}

// ------------------------------------------------------ //

void Sender::sendCommand( const Command &cmd, int timeout_s )
{
    std::string text;

    switch( cmd.Type )
    {
        case Helo:
            text = CommandTypeToString.at( Helo ) + " " + cmd.Data;
            break;
        case Ehlo:
            text = CommandTypeToString.at( Ehlo ) + " " + cmd.Data;
            break;
        case AuthPlain:
            text = CommandTypeToString.at( AuthPlain );
            break;
        case Mail:
            text = CommandTypeToString.at( Mail ) + ":<" + cmd.Data + ">";
            break;
        case Rcpt:
            text = CommandTypeToString.at( Rcpt ) + ":<"  + cmd.Data + ">";
            break;
        case Data:
            text = CommandTypeToString.at( Data );
            break;
        case Quit:
            text = CommandTypeToString.at( Quit );
            break;
        case Noop:
        case Rset:
        case AuthLogin:
        case Vrfy:
        case Expn:
        case Help:
        case UndefinedCommand:
        default:
            break;
    }

    sendText( text, timeout_s );
}

// ------------------------------------------------------ //

void Sender::sendText( const std::string &text, int timeout_s )
{
	if( !text.empty() )
		s_client.sendData( text + CRLF, timeout_s );
}
